#include "Block.hpp"


// documentation in Block.hpp

Block::Block() {
	data = NULL;
	length = 0;
}


Block::Block(const Block &other) {
	data = new unsigned char[other.getLength()];
	length = other.length;
	memcpy(data, other.getRO(), length);
}


Block::Block(unsigned int length) {
	data = new unsigned char[length];
	this->length = length;
}


Block::Block(unsigned int length, char pattern) {
	data = new unsigned char[length];
	this->length = length;
	memset(data, pattern, length);
}


Block::Block(const unsigned char* initData, unsigned int length) {
	data = new unsigned char[length];
	this->length = length;
	memcpy(data, initData, length);
}


Block::Block(const std::string &initData) {
	data = new unsigned char[initData.size()];
	length = initData.size();
	memcpy(data, initData.c_str(), length);
}


void Block::setToHex(const std::string &hexString) {
	if (hexString.size() % 2 != 0) {
		GENERICSTRMEXCEPTION("Length of hex string '" << hexString << "' is not divisible by 2 (is: " << hexString.size() << ").");
	}
	if (hexString.find_first_not_of("abcdefABCDEF0123456789") != std::string::npos) {
		GENERICSTRMEXCEPTION("Hex string '" << hexString << "' contains at least one invalid character.");
	}
	changeSize(hexString.size() / 2);
	for (unsigned int i = 0; i < length; i++) {
		unsigned char Current = 0;
		for (unsigned int j = 0; j < 2; j ++) {
			Current <<= 4;
			if ((hexString[2 * i + j] >= '0') && (hexString[2 * i + j] <= '9')) Current |= (hexString[2 * i + j] - '0');
				else if ((hexString[2 * i + j] >= 'a') && (hexString[2 * i + j] <= 'f')) Current |= (hexString[2 * i + j] - 'a' + 10);
				else if ((hexString[2 * i + j] >= 'A') && (hexString[2 * i + j] <= 'F')) Current |= (hexString[2 * i + j] - 'A' + 10);
		}
		data[i] = Current;
	}
}


void Block::changeSize(unsigned int length) {
	if (data) delete[] data;
	data = new unsigned char[length];
	this->length = length;
}


void Block::clear() const {
	if (data) memset(data, 0, length);
}


void Block::free() {
	if (data) delete[] data;
	data = NULL;
	length = 0;
}


void Block::truncate(unsigned int newLength) {
	if (newLength > length) GENERICSTRMEXCEPTION("Truncate may only make block smaller, not larger.");
	length = newLength;
}


unsigned char* Block::get() const {
	return data;
}


const unsigned char* Block::getRO() const {
	return data;
}


unsigned int Block::getLength() const {
	return length;
}


void Block::operator=(const Block &other) {
	if (&other == this) return;
	changeSize(other.length);
	if (length) memcpy(data, other.data, length);
}


bool Block::operator==(const Block &other) const {
	if (length != other.length) return false;
	for (unsigned int i = 0; i < length; i++) {
		if (data[i] != other.data[i]) return false;
	}
	return true;
}


bool Block::operator!=(const Block &other) const {
	return !((*this) == other);
}


void Block::operator^=(const Block &other) {
	for (unsigned int i = 0; i < (length < other.length ? length : other.length); i++) {
		data[i] ^= other.data[i];
	}
}


/* Be aware that the XOR-operation on blocks is *left* aligned. This means, e.g.
	* Block("foo") ^ Block("foobar") = Block("\0\0\0bar")
	*/
Block Block::operator^(const Block &other) const {
	Block Return;
	if (getLength() > other.getLength()) {
		Return = *this;
		Return ^= other;
	} else {
		Return = other;
		Return ^= *this;
	}
	return Return;
}


void Block::operator<<=(unsigned int bits) {
	if (((bits % 8) != 0)) throw GENERICEXCEPTION("Shift to number of bits not divisible by eight is not supported.");
	unsigned int bytes = bits / 8;
	if (bytes >= length) {
		clear();
		return;
	}
	for (unsigned int i = 0; i < length - bytes; i++) {
		data[i] = data[i + bytes];
	}
	memset(data + length - bytes, 0, bytes);
}


void Block::operator>>=(unsigned int bits) {
	if (((bits % 8) != 0)) throw GENERICEXCEPTION("Shift to number of bits not divisible by eight is not supported.");
	unsigned int bytes = bits / 8;
	if (bytes >= length) {
		clear();
		return;
	}
	for (int i = length - bytes - 1; i >= 0; i--) {
		data[i + bytes] = data[i];
	}
	memset(data, 0, bytes);
}


void Block::dump(std::ostream &outStrm) const {
	outStrm << "Length: " << length << std::endl;
	outStrm << "Data  : ";
	outStrm << std::hex;
	for (unsigned int i = 0; i < length; i++) {
		outStrm << std::setfill('0') << std::setw(2) << (int)data[i];
	}
	outStrm << std::dec << std::endl;
	outStrm << "Text  : '";
	for (unsigned int i = 0; i < length; i++) {
		if ((data[i] >= 32) && data[i] <= 126) outStrm << data[i];
			else outStrm << ".";
	}
	outStrm << "'" << std::endl;
	outStrm << std::endl;
}


void Block::shortDump(std::ostream &outStrm, const std::string &caption) const {
	outStrm << std::setfill(' ') << std::setw(16) << caption << " [" << std::setw(3) << length << "] ";

	std::stringstream Strm;
	Strm << std::hex;
	for (unsigned int i = 0; i < ((length > 40) ? 40 : length); i++) {
		Strm << std::setfill('0') << std::setw(2) << (int)data[i];
	}
	outStrm << std::setw(80) << Strm.str() << "    ";
	for (unsigned int i = 0; i < ((length > 40) ? 40 : length); i++) {
		if ((data[i] >= 32) && data[i] <= 126) outStrm << data[i];
			else outStrm << ".";
	}
	if (length > 40) outStrm << " MORE";
	outStrm << std::dec << std::endl;
}

Block::~Block() {
	if (data) delete[] data;
}
